<?php

class RootModel
{

    private $c;

    private $deep;

    function __construct($deep, $c)
    {
        $this->c = $c;
        $this->deep = $deep;
    }

    function create($model = [])
    {
        $this->verify($model);
        //插入model, with createTime
        $empty = [];
        $this->c->insert($empty);
        $model = [
                'id' => $empty['_id']->__toString(),
                'stat' => ['createTime' => time()]
            ] + $model;

        $this->c->update(['_id' => new MongoId($model['id'])], $model);

        return $model['id'];
    }

    function remove($id)
    {
        $result = $this->c->remove(['id' => $id]);

        if ($result['n'] === 0) {
            throw new IdNotExistException($id, $this->c->getName());
        }
    }

    function find($id, $fields = null)
    {
        $model = $this->findUnique(['id' => $id], $fields);

        if ($model === null) {
            throw new IdNotExistException($id, $this->c->getName());
        }

        return $model;
    }

    function findUnique($cond, $fields = null)
    {
        return $this->c->findOne(
            $this->toMongoCond($cond),
            $this->toMongoFields($fields)
        );
    }

    function findBy($args)
    {
        $fields = key_exists('fields', $args) ? $args['fields'] : null;
        $cond = key_exists('cond', $args) ? $args['cond'] : [];
        $orderBy = key_exists('orderBy', $args) ? $args['orderBy'] : 'stat.createTime dsc';
        $skip = $args['skip'];
        $limit = $args['limit'];

        $cursor = $this->c->find(
            $this->toMongoCond($cond),
            $this->toMongoFields($fields)
        )->sort($this->toMongoOrderBy($orderBy))
            ->skip($skip)->limit($limit);

        //list & total
        $count = $cursor->count();
        $list = iterator_to_array($cursor);

        return ['total' => $count, 'list' => $list];
    }

    function __get($name)
    {
        //从deep中返回
        if (key_exists($name, $this->deep)) return $this->deep[$name];
        else throw new MissingFieldException(
            $name, $this->c->getName());
    }

    function verify($value)
    {
        //首先确定其是对象
        if (!is_array($value) || key_exists(0, $value)) {
            throw new IllegalModelException(
                'Expect Object', $this->c->getName());
        }
        foreach ($value as $fieldName => $fieldValue) {
            if (!key_exists($fieldName, $this->deep)) {
                throw new IllegalModelException(
                    'Not Exist', $this->c->getName());
            }
            $this->deep[$fieldName]->verify($fieldValue);
        }


        return null;
    }

    private function toMongoFields($fields)
    {
        if (is_string($fields)) $fields = [$fields];

        $mongoFields = ['_id' => 0];

        if ($fields) {
            foreach ($fields as $field) {
                $mongoFields[$field] = 1;
            }
        }

        return $mongoFields;

    }

    private function toMongoCond($cond)
    {
        return $cond;
    }

    private function toMongoOrderBy($orderBy)
    {
        if (is_string($orderBy)) $orderBy = [$orderBy];

        $mongoOrderBy = [];

        foreach ($orderBy as $orderToken) {
            $orderTokens = explode(' ', $orderToken);
            $orderField = $orderTokens[0];
            $orderType = (count($orderTokens) > 1) ? $orderTokens[1] : 'asc';
            switch ($orderType) {
                case 'asc':
                    $mongoOrderBy[$orderField] = 1;
                    break;
                case 'dsc':
                    $mongoOrderBy[$orderField] = -1;
                    break;
                default:
                    throw new InvalidArgumentException(
                        'Unknown order type: ' . $orderToken);
            }
        }

        return $mongoOrderBy;
    }

}
